package com.qisimanxiang.dalaran.zuul.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author wangmeng
 * @date created in 11:13 上午 2019/9/18
 * @modified by
 */
@Slf4j
@Component
public class RedisHelper {
    @Autowired
    private RedisTemplate<Object, Object> template;

    private static RedisHelper INSTANCE;
    public static boolean available = false;

    public RedisHelper() {
        INSTANCE = this;
    }

    @PostConstruct
    public void init() throws InterruptedException {
        template.setKeySerializer(template.getStringSerializer());
        available = true;
    }


    public static void release(String key) {
        INSTANCE.p_release(key);
    }

    public static boolean tryAcquire(String key, Integer total, int timeout) {
        return INSTANCE.p_tryAcquire(key, total, timeout);
    }


    private boolean p_tryAcquire(String key, Integer total, int timeout) {
        Long time = System.currentTimeMillis();
        Long endTime = time + timeout;
        boolean acquireOk = false;
        while ((!acquireOk) && (time <= endTime)) {
            acquireOk = p_tryAcquire(key, total);
            if (!acquireOk) {
                time = System.currentTimeMillis();
            }
        }
        return acquireOk;
    }

    private static final String sc = "redis.call(\"incrby\",KEYS[1],0) " +
            " if tonumber(redis.call(\"get\",KEYS[1])) < tonumber(ARGV[1]) then     return redis.call(\"incr\",KEYS[1]) else     return 0 end";

    private boolean p_tryAcquire(String key, Integer total) {
        try {
            Long execute = template.execute((RedisCallback<Long>) connection ->
                    connection.eval(template.getStringSerializer().serialize(sc), ReturnType.INTEGER, 1,
                            template.getStringSerializer().serialize(key), template.getStringSerializer().serialize(total.toString())));
            return execute != null && execute > 0;
        } catch (Exception e) {
            log.info("redis exception", e);
            available = false;
            return false;
        }
    }


    private void p_release(String key) {
        try {
            template.boundValueOps(key).decrement();
        } catch (Exception e) {
            log.info("redis exception", e);
            available = false;
        }
    }

    @Scheduled(fixedRate = 5000)
    private void checkRedis() {
        try {
            template.boundValueOps(this.getClass().getSimpleName()).get();
            log.info("redis check success.");
            available = true;
        } catch (Exception e) {
            log.error("redis exception: {}", e.getMessage());
            available = false;
        }
    }

}
